package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.management.MBeanServerConnection;
import java.io.File;
import java.io.FileFilter;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    private Map<String, Object> singletonMap = new ConcurrentHashMap<>();  // 存储单例对象
    private Map<String, BeanDefinition> definitionMap = new ConcurrentHashMap<>();  // 存储definition对象
    private Map<Class, String> beanNameForTypeMap = new ConcurrentHashMap<>(); // 通过type找到id
    private Map<String, Object> unfinishedMap = new ConcurrentHashMap<>();

    public BeanFactory(String path) {
        loadResource(path);
    }

    // 返回一个已经加载好的实例
    public static BeanFactory createBeanFactory(String path) {
        return new BeanFactory(path);
    }

    private void loadResource(String path) {
        // 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream(path);
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            //包扫描相关内容
            scanPackage(rootElement);

            // 扫描bean, 加载class文件
            LoadBean(rootElement);

            // 初始化bean
            initiateBean();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initiateBean() throws Exception {
        for(Map.Entry<String, BeanDefinition> entry: definitionMap.entrySet()) {
            getBean(entry.getKey());
        }
    }

    private void parseAttribute(Element parentElement, BeanDefinition bd) throws Exception {
        // 实例化完成之后维护对象的依赖关系，检查哪些对象需要传值进入，根据它的配置，我们传入相应的值
        // 有property子元素的bean就有传值需求
        List<Element> propertyList = parentElement.selectNodes("./property");
        // 解析property，获取父元素
        for (int i = 0; i < propertyList.size(); i++) {
            Element element = propertyList.get(i);   //<property name="AccountDao" ref="accountDao"></property>
            String name = element.attributeValue("name");
            String ref = element.attributeValue("ref");
            String value = element.attributeValue("value");

            // 装载raw属性
            if (value != null && !"".equals(value)) {
                bd.setRawAttribute(name, value);
            }

            // 装载ref属性
            if (ref != null && !"".equals(ref)) {
                bd.setRefAttribute(name, ref);
            }
        }
    }

    /**
     * 装载xml中的每个bean
     * @param rootElement
     * @throws Exception
     */
    private void LoadBean(Element rootElement) throws Exception {
        List<Element> beanList = rootElement.selectNodes("//bean");
        for (int i = 0; i < beanList.size(); i++) {
            Element element =  beanList.get(i);
            // 处理每个bean元素，获取到该元素的id 和 class 属性
            String id = element.attributeValue("id");        // accountDao
            String clazz = element.attributeValue("class");  // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
            // 装载beanDefinition
            Class<?> aClass = Thread.currentThread().getContextClassLoader().loadClass(clazz);
            BeanDefinition bd = new BeanDefinition(aClass);
            // 将type和name关联
            parseTypeToName(aClass, id);
            // 解析xml中的值
            parseAttribute(element, bd);

            // 存储到definition中
            definitionMap.put(id, bd);
        }
    }

    /**
     * 将类型和名称关联
     * @param aClass
     */
    private void parseTypeToName(Class<?> aClass, String name) {
        // 将interface放入map
        Class<?>[] interfaces = aClass.getInterfaces();
        for(Class inf: interfaces) {
            beanNameForTypeMap.put(inf, name);
        }

        // 将class也放入map
        beanNameForTypeMap.put(aClass, name);
    }

    /**
     * 获取所有需要扫描的路径，开始扫描
     * @param rootElement
     */
    private void scanPackage(Element rootElement) {
        List<Element> packageList = rootElement.selectNodes("//component-scan");
        String basePath;
        for(Element pg: packageList) {
            basePath = pg.attribute("base-package").getValue();
            doScanPackage(basePath);
        }
    }

    /**
     * 获取所有扫描路径，并加载路径下的包
     * @param packageName
     */
    private void doScanPackage(String packageName) {
        String dirPath = packageName.replace('.', '/');
        Enumeration<URL> resources;
        try {
            // 获取所有被扫描路径
            resources = Thread.currentThread().getContextClassLoader().getResources(dirPath);
            while (resources.hasMoreElements()) {
                URL url = resources.nextElement();
                String file = url.getFile();

                // 加载包下的class
                loadClasses(packageName, file);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载class文件
     * @param packageName
     * @param path
     */
    private void loadClasses(String packageName, String path) {
        File dir = new File(path);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
            return;
        }
        // 如果存在 就获取包下的所有文件和目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            public boolean accept(File file) {
                // 只关注.class文件
                return file.getName().endsWith(".class") || file.isDirectory();
            }
        });
        if (dirfiles != null) {
            // 循环所有文件
            for (File file : dirfiles) {
                try {
                    if (file.getName().endsWith(".class")) {
                        String className = file.getName().substring(0, file.getName().length() - 6);
                        // 加载到内存
                        Class<?> loadClass = Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className);

                        // 如果有service注解就放入map中待用
                        if (loadClass.isAnnotationPresent(Service.class)) {
                            // 创建bean定义
                            BeanDefinition bd = new BeanDefinition(loadClass);

                            // 获取名称
                            Service service = loadClass.getAnnotation(Service.class);
                            String name = service.value();
                            if ("".equals(name)) {
                                name = loadClass.getSimpleName();
                            }

                            // 创建type和name的映射，并解析autowired
                            parseTypeToName(loadClass, name);
                            parseAttribute(loadClass, bd);

                            definitionMap.put(name, bd);
                        }
                    } else if(file.isDirectory()) {
                        loadClasses(packageName+'.'+file.getName(), file.getAbsolutePath());
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 解析需要注入的属性
     * @param loadClass
     * @param bd
     */
    private void parseAttribute(Class<?> loadClass, BeanDefinition bd) {
        Field[] fields = loadClass.getDeclaredFields();
        for (Field field: fields) {
            if(field.isAnnotationPresent(Autowired.class)) {
                bd.setRefAttribute(field.getName(), field.getType());
            }
        }
    }

    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public Object getBean(String id) {
        Object o = getRealBean(id);
        if (o == null) {
            // 没考虑创建失败的销毁问题
            try {
                return createBean(id);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return o;
    }

    public Object getBean(Class type) {
        String id = beanNameForTypeMap.get(type);
        return getBean(id);
    }

    /**
     * 创建bean
     * @param id
     * @return
     * @throws Exception
     */
    private Object createBean(String id) throws Exception {
        Object bean;
        BeanDefinition beanDefinition;
        Class<?> beanClass;

        // 确保只有一个进入创建流程
        synchronized (this) {
            // 之前get没有保证不冲突（提高效率），此处再验证一次
            bean = getRealBean(id);
            if (bean != null) {
                return bean;
            }
            beanDefinition = definitionMap.get(id);
            if (beanDefinition == null) {
                throw new Exception("no definiton: "+ id);
            }
            beanClass = beanDefinition.getBeanClass();
            bean = beanClass.newInstance();
            unfinishedMap.put(id, bean);
        }
        setAttribute(beanDefinition, bean, beanClass);

        bean = afterProcess(bean, beanClass);

        // 移除过程保证不冲突
        synchronized (this) {
            unfinishedMap.remove(id);
            singletonMap.put(id, bean);
        }
        return bean;
    }

    /**
     * 后处理过程，目前只处理事务问题
     * @param bean
     * @param beanClass
     */
    private Object afterProcess(Object bean, Class<?> beanClass) {
        Class<?>[] interfaces = beanClass.getInterfaces();
        Annotation tx;
        // 处理接口上的transactional
        for(Class inf: interfaces) {
            tx = inf.getAnnotation(Transactional.class);
        }

        // 处理类上的
        tx = beanClass.getAnnotation(Transactional.class);

        // 处理每个方法上的注解
        Method[] methods = beanClass.getDeclaredMethods();
        Transactional tsc;
        HashSet<Method> methodHashSet = new HashSet<>();
        for(Method method: methods) {
            tsc = method.getAnnotation(Transactional.class);
            if (tx != null || tsc != null) {
                methodHashSet.add(method);
            }
        }

        // 如果有注解就生成代理对象, 只用实现cglib, 不然在校验对象类型阶段可能会导致失败（目前没有这个问题）
        if (methodHashSet.size() > 0) {
            bean = ((ProxyFactory) getBean("proxyFactory")).getCglibProxy(bean, methodHashSet);
        }
        return bean;
    }

    private void setAttribute(BeanDefinition bd, Object o, Class type) throws Exception {
        // 设置普通属性
        Map<String, Object> rawAttributes = bd.getRawAttributes();
        for(Map.Entry<String, Object> entry: rawAttributes.entrySet()) {
            Field field = type.getDeclaredField(entry.getKey());
            field.set(o, entry.getValue());
        }

        // 设置ref属性
        Map<String, Object> refAttributes = bd.getRefAttributes();
        for(Map.Entry<String, Object> entry: refAttributes.entrySet()) {
            // 两种情况，type注入--class和id注入--string
            Object value = entry.getValue();
            Object refVal = null;
            if (value instanceof Class) {
                refVal = getBean((Class) value);
            } else if (value instanceof String) {
                refVal = getBean((String) value);
            }
            if (refVal == null) {
                throw new Exception("no such ref: " + value);
            }
            String key = entry.getKey();
            Field field = type.getDeclaredField(key);
            field.setAccessible(true);
            field.set(o, refVal);
        }
    }

    /**
     * 获取bean
     * @param id
     * @return
     */
    public Object getRealBean(String id) {
        Object o = singletonMap.get(id);
        if(o == null) {
            o = unfinishedMap.get(id);
        }
        return o;
    }
}
